﻿using System;
using Bittorrent.Extentions;
using Bittorrent.Bencode.Collections;
using System.IO;
using Bittorrent.Bencode;
using System.Diagnostics.Contracts;
using Bittorrent.File.Exceptions;

namespace Bittorrent.File
{
    public class File
    {
        private readonly Dictionary _dictionary;

        #region RegionName
        /// <summary>
        /// Initializes a new instance of the File class.
        /// </summary>
        public File() {
            UId = -1;
            CreationDate = DateTime.MinValue;
        }
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="File"/> class.
        /// </summary>
        /// <param name="path">The path to the torrent file.</param>
        public File(string path) {
            _dictionary = GetDictonaryFromFile(path);

            Announce = GetAnnounce(_dictionary);
            CreationDate = GetCreationDate(_dictionary);
            CreatedBy = _dictionary.GetOptionalString("created by");
            Commment = _dictionary.GetOptionalString("comment");
            Info = GetInfo(_dictionary);
            UId = _dictionary.GetOptionalInteger("uid");
        }

        /// <summary>
        /// The announce URL of the tracker.
        /// </summary>
        public Uri Announce { get; set; }

        /// <summary>
        /// The creation time of the torrent.
        /// This property is optional if it doese not exist it Returns DateTime.MinValue.
        /// </summary>
        public DateTime CreationDate { get; set; }

        /// <summary>
        /// Name and version of the program used to create the torrent file.
        /// This property is optional and may return an empty string if not available.
        /// </summary>
        public string CreatedBy { get; set; }

        /// <summary>
        /// Textual comments from the author.
        /// This property is optional and may return an empty string if not available.
        /// </summary>
        public string Commment { get; set; }

        public long UId { get; set; }

        /// <summary>
        /// Information describing the file(s) of the torrent
        /// </summary>
        public Info Info { get; private set; }

        public void Save(string path) {
            var dictionary = new Dictionary();

            UriBuilder uriBuilder = new UriBuilder(Announce);

            dictionary.Add((ByteString)"announce", new ByteString { String = uriBuilder.ToString() });

            DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            if (CreationDate != DateTime.MinValue || CreationDate > epoch) {
                TimeSpan span = (CreationDate - epoch);
                dictionary.Add((ByteString)"creation date", new Integer { Value = (long)span.TotalSeconds });
            }

            if (!string.IsNullOrEmpty(CreatedBy)) {
                dictionary.Add((ByteString)"created by", new ByteString { String = CreatedBy });
            }

            if (!string.IsNullOrEmpty(Commment)) {
                dictionary.Add((ByteString)"comment", new ByteString { String = Commment });
            }

            dictionary.Add((ByteString)"info", Info.GetDictionary());

            if (UId != -1) {
                dictionary.Add((ByteString)"uid", new Integer { Value = UId });
            }

            var fileStream = System.IO.File.Create(path);
            byte[] buffer = Bencode.BencodeFactory.Encode(dictionary);
            fileStream.Write(buffer, 0, buffer.Length);
        }

        private static Dictionary GetDictonaryFromFile(string path) {
            if (!System.IO.File.Exists(path)) {
                throw new FileNotFoundException(path);
            }

            byte[] bytes = System.IO.File.ReadAllBytes(path);

            var dictonary = BencodeFactory.Decode(bytes) as Dictionary;
            if (dictonary == null) {
                throw new TorrentFileException("Not starting with a Bencode.Diconary");
            }

            return dictonary;
        }

        private static Uri GetAnnounce(Dictionary dictionary) {
            if (!dictionary.ContainsKey("announce")) {
                throw new TorrentFileException("There is no announce url set for the torrent file");
            }

            var announce = dictionary["announce"] as ByteString;
            if (announce == null) {
                throw new TorrentFileException("The value representing announce was not the expected ByteString");
            }

            return new Uri(announce.String);
        }

        private static DateTime GetCreationDate(Dictionary dictionary) {
            if (!dictionary.ContainsKey("creation date")) {
                return DateTime.MinValue;
            }

            var benObj = dictionary["creation date"];
            // I will not throw an exception because creation date is optional
            if (!(benObj is Integer)) {
                return DateTime.MinValue;
            }

            long seconds = ((Integer)benObj).Value;

            // Convert from UNIX epoch format to DateTime
            var result = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            
            return result.AddSeconds(seconds);
        }

        private static Info GetInfo(Dictionary dictionary) {
            Contract.Requires(dictionary != null);
            if (!dictionary.ContainsKey("info")) {
                throw new TorrentFileException("There was no info Bencode.Dictonary in the torrent file");
            }

            var info = dictionary["info"] as Dictionary;
            if (info == null) {
                throw new TorrentFileException("The value representing info was not the expected Bencode.Dictonary");
            }

            return new Info(info);
        }
    }
}